Summary: Generating digimode transmissions like THOR can be done directly on radio frequency with cheap dds-chip like ad9850 and Arduino, or similar system. With setup mentioned and code described in this page, THOR-transmissions can be generated at any frequency lower than about 40 Mh.
Late autumn forestwalk, testing THOR-transmitter. .
This code produces THOR-transmission with ad9850-dds chip controlled by Arduino. I have used modified version of
code from webshed , made by David Mills, to control ad9850, and build THOR-modulator on top of that. I have used exactly same hardware connections as on example on webshed. (Altough nowadays I use hardware SPI instead of shiftOut-function, but for slow THOR-transmissions it doesn't really matter.)
This code will not work with original webshed-ad9850.driver, but use the modified one introduced on WSPR from AD9850 -page.
Picture: This is my Very Integrated Development Environment....mostly all is on steel kabin from Ikea, which is assembled 90 degree wrong and can be closed also.There is a car battery behind brown panel powering everything when main power failures.BTW this is our livingroom...as you can see from walls, Finland is still in medieval age...
This is only signal source, to "real" transmitter you may want to have also some amplifier and filters. Tx power generated from ad9850 is around 10mw. (Altought it is absolutely enough for many interesting projects.)
If you want to try transmit THOR with your Arduino, copy hardware connections from webshedand function from this page. You need to call this funtion inside loop(). For example:
thor("This is message), 28500000, 8)
...should sent "This is message" at 28,5 Mhz, at baudrate of 8.There is new line character ( char(10) ) added at start and end of transmission, for comfortable reading.
This function is tested with Arduino Mega2560 and Teensy 3.1. I see no reason why it would not work with Uno.
How to transmit digimodes in so very neat way...(at your dreams...)
Usually digimodes are transmitted with normal ssb-transmitter, by injecting audio signal from computer sound card to microphone connection or similar in rig. So digital modulation is done first on audio frequency and this audio is shifted up to radio frequency on ssb-transmitter. Only reason for this is to use already existing hardware, there are some possible difficulties to overcome: rf-feedback to audio, need for digimode interface with proper isolation, and so on.
And (in theory) ssb-transmitter is just overly complicated (=expensive+powerhungry+big) for that work.
In real life our hacks are bigger, more complicated and power-wasting as usual rigs of course, and with more trouble all kind, and just so much more interesting.
This experiment is based on my DominoEX -project.
In principle THOR is very much like Domino, but with added strong error correction.
There is more technical introduction of THOR-mode on website of W1HKJ. (Link goes to Fldigi user manual. Fldigi is digimode program I have used to receive signal generated from this project, for testing it.)
Work is made on Arduino IDE 1.0.5. Other versions may need changes for example to use PROGMEM I believe.
Varicode characters are defined on array of ints on PROGMEM. (Picture) Defining array is done such a odd-looking way in order to avoid mistakes, or at least to make possible to correct them.
Characters are taken from here.(Work of Nino Porcino IZ8BLY & M. Greenman ZL1BPU) where they are defined in varying lenght of bitrows. Those bitrows are converted to desimal values and stored to int. (Because in Arduino IDE only byte can be defined like B01010... not int, but there are varicode characters longer than eight bits, so int must be used to store them.)
When I was making this array, making one row was about ten-step process. And there are 256 rows...
Don't be confused when you find out that these are "MFSK-varicode characters", yes they are. Same are used on THOR.
Oddity report: character 'a' on row number 97 had to be dublicated. Otherwise characters with line numbers greater than 97 would not work correct, but 'a' would turn to be 'b' , 'b' would turn to be 'c' and so on ( or was it opposite..?). Reason is currently unknown.
[[code]]
**First shift register, fecIn:** Unsigned long f<span style="line-height: 1.5;">ecIn is used as shift register. FEC-coder takes bits on "right" side of fecIn. Variable inFec is used to keep count of how many bits are in use at fecIn, and what place next bits should be inserted.</span>
If next character is not 0, and if there is enough room for it in fecIn, b<span style="line-height: 1.5;">its of varicode characters are placed on fecIn. Variable bitsInChar is used to discard mentioned unused start-zeros on int nextChar.</span>
Note that nextChar is variable for the actual next character but when it is set to 0 (after varicode is inserted to fecIn), it means it is ready to be filled with next char.
<span style="line-height: 1.5;">**Forward Error Correction:** I found needed documentation for FEC [[http://www.qsl.net/zl1bpu/MFSK/FECcoder.htm| here]]. (Work of </span><span style="font-family: arial,helvetica; font-size: 14px; line-height: 1.5;">Nino Porcino IZ8BLY & M. Greenman ZL1BPU). </span><span style="font-family: arial,helvetica; font-size: 13.600000381469727px; line-height: 1.5;">If you look that page, you will find algorithm which is actually at use in FEC-coder.</span>
<span style="font-family: arial,helvetica; font-size: 13.600000381469727px; line-height: 1.5;"> Shortly for one varicode bit inserted in shift register fecIn, scetch produces two FEC-coded bits to be sent to interliever. (FEC is reason why speed of THOR11 is only half of Domino11).</span>
After all FEC-coded bits will appear to variable fecOut, which again is shift register. Interliever will take bytes from there:
**Interliever:**
Interliever spreads each symbols on transmission, to be transmitted over longer period. Therefore short interference bursts, like noise from flashes are easier to correct when receiving. After code below variable toTx will contain interlieved 4-bit graycode for transmitter to be sent. First of those 4 bits is never delayed, second bit is delayed 10 symbol, third 20 symbol and fourth 30 symbol. For example at THOR11 period of one symbol is 93ms, so fourth bit is delayed 2790ms, or 2,8 seconds. So each character on transmission is spread over time (...is this kind of time domain spread-spectrum technique?).
Interliever shift registers are numbered from 2 to 4...there was originally also interliever1, but I later realized it is not needed because first bit is never shifted. So first bit is taken from fecOut and it is directly written to toTx.
INTERLEAVER is simply bit shifter. or "delayer"
TO INTERLIEVER:
bitWrite(toTx,3, bitRead(fecOut,3)); No delay for 1.bit, it is goes straight to toTx
bitWrite(interleaver2, 0, bitRead(fecOut,2));delay 10 bits, or shifts bitWrite(interleaver3, 0, bitRead(fecOut,1));delay 20 bits, or shifts
bitWrite(interleaver4, 0, bitRead(fecOut,0));delay 30 bits, or shifts
FROM INTERLIEVER: toTx,3, is already filled bitWrite(toTx,2,(bitRead(interleaver2,10))); bitWrite(toTx,1,(bitRead(interleaver3,20))); bitWrite(toTx,0,(bitRead(interleaver4,30)));
Interleaver is bitshifter, so lets shift it:
interleaver2= interleaver2 << 1;
interleaver3 = interleaver3 << 1;
interleaver4 = interleaver4 << 1;
// toTx is now ready to be transmitted, and it contains 4-bit gray-code, corresponding to tone difference for next symbol or "tone"
THOR from ad9850
Summary:
Generating digimode transmissions like THOR can be done directly on radio frequency with cheap dds-chip like ad9850 and Arduino, or similar system. With setup mentioned and code described in this page, THOR-transmissions can be generated at any frequency lower than about 40 Mh.
Late autumn forestwalk, testing THOR-transmitter.
.
This code produces THOR-transmission with ad9850-dds chip controlled by Arduino. I have used modified version of
code from webshed , made by David Mills, to control ad9850, and build THOR-modulator on top of that. I have used exactly same hardware connections as on example on webshed. (Altough nowadays I use hardware SPI instead of shiftOut-function, but for slow THOR-transmissions it doesn't really matter.)
This code will not work with original webshed-ad9850.driver, but use the modified one introduced on WSPR from AD9850 -page.
Picture: This is my Very Integrated Development Environment....mostly all is on steel kabin from Ikea, which is assembled 90 degree wrong and can be closed also.There is a car battery behind brown panel powering everything when main power failures.BTW this is our livingroom...as you can see from walls, Finland is still in medieval age...
This is only signal source, to "real" transmitter you may want to have also some amplifier and filters. Tx power generated from ad9850 is around 10mw. (Altought it is absolutely enough for many interesting projects.)
If you want to try transmit THOR with your Arduino, copy hardware connections from webshedand function from this page. You need to call this funtion inside loop(). For example:
thor("This is message), 28500000, 8)
...should sent "This is message" at 28,5 Mhz, at baudrate of 8.There is new line character ( char(10) ) added at start and end of transmission, for comfortable reading.
This function is tested with Arduino Mega2560 and Teensy 3.1. I see no reason why it would not work with Uno.
How to transmit digimodes in so very neat way...(at your dreams...)
Usually digimodes are transmitted with normal ssb-transmitter, by injecting audio signal from computer sound card to microphone connection or similar in rig. So digital modulation is done first on audio frequency and this audio is shifted up to radio frequency on ssb-transmitter. Only reason for this is to use already existing hardware, there are some possible difficulties to overcome: rf-feedback to audio, need for digimode interface with proper isolation, and so on.
And (in theory) ssb-transmitter is just overly complicated (=expensive+powerhungry+big) for that work.
In real life our hacks are bigger, more complicated and power-wasting as usual rigs of course, and with more trouble all kind, and just so much more interesting.
This experiment is based on my DominoEX -project.
In principle THOR is very much like Domino, but with added strong error correction.
There is more technical introduction of THOR-mode on website of W1HKJ. (Link goes to Fldigi user manual. Fldigi is digimode program I have used to receive signal generated from this project, for testing it.)
Work is made on Arduino IDE 1.0.5. Other versions may need changes for example to use PROGMEM I believe.
//THOR-modulator /* Function for produsing THOR transmissions with ad9850 DDS-module and teensy or Arduino (tested with Mega2560 and teensy 3.1) This function have to be called from loop() with parameters thor("String message", long txfrequency, byte baudrate) for example : thor("KISS you", 28500000, 8); Frequency is in hertzs. Baudrates are 4,5,8,11,16 and 22 which is default and used also if wrong baudrate is given. This function requires also driver snippet for ad9850, which is available from KISS ACTION GROUP website. Tarmo Huttunen OH6ECF 2015 You can use this code as you like. If you develop it better, please share it. */ //Varicode characters are stored on this funny-looking array prog_uint16_t PROGMEM varicodeTable[] ={ 1884,//0 <NUL> 1888,//1 <SOH> 1896,//2 <STX> 1900,//3 <ETX> 1904,//4 <EOT> 1908,//5 <ENQ> 1912,//6 <ACK> 1916,//7 <BEL> 168,//8 <BS> 1952,//9 <TAB> 1952,//10 <LF> 1960,//11 <VT> 1964,//12 <FF> 172,//13 <CR> 1968, //14 <SO> 1972,//15 <SI> 1976,//16 <DLE> 1980,//17 <DC1> 1984,//18 <DC2> 2000,//19 <DC3> 2004,//20 <DC4> 2008,//21 <NAK> 2012,//22 <SYN> 2016,//23 <ETB> 2024,//24 <CAN> 2028,//25 <EM> 2032,//26 <SUB> 2036,//27 <ESC> 2040,//28 <FS> 2044,//29 <GS> 2048,//30 <RS> 2560,//31 <US> 4,//32 <SPACE> 448,//33 ! 508,//34 " 728,//35 # 680, //36 $ 672,//37 % 512, //38 & 444, //39 ' 500, //40 ( 496,//41 ) 692,//42 * 480,//43 + 160,//44 , 472,//45 - 468,//46 . 488,//47 / 224, //48 0 240, //49 1 320, //50 2 340,//51 3 372,//52 4 352,//53 5 364,//54 6 416,//55 7 384,//56 8 428,//57 9 492,//58 : 504,//59 ; 704,//60 < 476,//61 = 700,//62 > 464,//63 ? 640,//64 @ 188,//65 A 256,//66 B 212,//67 C 220,//68 D 184,//69 E 248,//70 F 336,//71 G 344,//72 H 192,//73 I 436,//74 J 380,//75 K 244,//76 L 232,//77 M 252,//78 N 208,//79 O 236,//80 P 432,//81 Q 216,//82 R 180,//83 S 176,//84 T 348,//85 U 424,//86 V 360,//87 W 368,//88 X 376,//89 Y 440,//90 Z 744,//91 ( 720,//92 \ 748,//93 ) 724,//94 ^ 688,//95 _ 684,//96 ` 20,//97 a //INTENTIONALLY DUBLICATED LINE 20,//97 a //INTENTIONALLY DUBLICATED LINE 96,//98 b 56,//99 c 52,//100 d 8,//101 e 80,//102 f 88,//103 g 48,//104 h 24,//105 i 128,//106 j 112,//107 k 44,//108 l 64,//109 m 28,//110 n 16,//111 o 84,//112 p 120,//113 q 32,//114 r 40,//115 s 12, //116 t 60,//117 u 108,//118 v 104,//119 w 116,//120 x 92,//121 y 124,//122 z 732,//123 { 696,//124 | 736,//125 } 752,//126 ~ 2688,//127 <DEL> 2720,//128 ? 2728,//129 ‚ 2732,//130 ‚ 2736,//131 ƒ 2740,//132 „ 2744,//133 … 2748,//134 † 2752,//135 ‡ 2768,//136 ˆ 2772,//137 ‰ 2776,//138 Š 2780,//139 ‹ 2784,//140 Œ 2792,//141 //I DO not KNOW WHAT THIS IS 2796,//142 Z 2800,//143 //I DO not KNOW WHAT THIS IS 2804,//144 //I DO not KNOW WHAT THIS IS 2808,//145 ‘ 2812,//146 ’ 2816,//147 “ 2880,//148 ” 2896,//149 • 2900,//150 – 2904,//151 — 2908,//152 ˜ 2912,//153 ™ 2920,//154 š 2924,//155 › 2928,//156 œ 2932,//157 //I DO not KNOW WHAT THIS IS 2936,//158 z 2940,//159 Ÿ 756,//160 //I DO not KNOW WHAT THIS IS 760,//161 ¡ 764,//162 ¢ 768,//163 £ 832,//164 ¤ 848,//165 ¥ 852,//166 ¦ 856,//167 § 860,//168 ¨ 864,//169 © 872,//170 ª 876,//171 « 880,//172 ¬ 884,//173 //I DO not KNOW WHAT THIS IS 888,//174 ® 892,//175 ¯ 896,//176 ° 928,//177 ± 936,//178 ² 940,//179 ³ 944,//180 ´ 948,//181 µ 952,//182 ¶ 956,//183 · 960,//184 ¸ 976,//185 ¹ 980,//186 º 984,//187 » 988,//188 ¼ 992,//189 ½ 1000,//190 ¾ 1004,//191 ¿ 1008,//192 À 1012,//193 Á 1016,//194  1020,//195 à 1024,//196 Ä 1280,//197 Å 1344,//198 Æ 1360,//199 Ç 1364,//200 È 1368,//201 É 1372,//202 Ê 1376,//203 Ë 1384,//204 Ì 1388,//205 Í 1392,//206 Î 1396,//207 Ï 1400,//208 Ð 1404,//209 Ñ 1408,//210 Ò 1440,//211 Ó 1448,//212 Ô 1452,//213 Õ 1456,//214 Ö 1460,//215 × 1464,//216 Ø 1468,//217 Ù 1472,//218 Ú 1488,//219 Û 1492,//220 Ü 1496,//221 Ý 1500,//222 Þ 1504,//223 ß 1512,//224 à 1516,//225 á 1520,//226 â 1524,//227 ã 1528,//228 ä 1532,//229 å 1536,//230 æ 1664,//231 ç 1696,//232 è 1704,//233 é 1708,//234 ê 1712,//235 ë 1716,//236 ì 1720,//237 í 1724,//238 î 1728,//239 ï 1744,//240 ð 1748,//241 ñ 1752,//242 ò 1756,//243 ó 1760,//244 ô 1768,//245 õ 1772,//246 ö 1776,//247 ÷ 1780,//248 ø 1784,//249 ù 1788,//250 ú 1792,//251 û 1856,//252 n 1872,//253 ý 1876,//254 þ 1880,//255 }; void thor(String txString, unsigned long frequency, byte baud){ unsigned long thorTimer = millis(); byte next = 0; //used to count frequency of next symbol boolean dibit1; boolean dibit2; byte fecOut = B00000000; unsigned long fecIn = 0; int inFec = 0;//counting bits on fecIn byte bitInCounter = 0; byte bitsInChar = 0; //These are used as shift registers on interliever byte interleaver1 = 0; int interleaver2 = 0; long interleaver3 = 0; long interleaver4 = 0; // byte toTx = 0; byte thorOn = 1; unsigned int nextChar = 0;//actual char, 0 if ready to tx //byte nextCharStart = 0; //If actual char is over 8 bit long, this is the start of char byte idleTime = 0; //counter for idle timer int round1 = 0; unsigned int symbolTime; //length of one tone float spacing; String thorMessage = txString; thorMessage = char(10) + thorMessage + char(10); int length = thorMessage.length(); AD9850_reset(); AD9850_init(); switch(baud){ case 4: { symbolTime = 256; spacing = 7.8125; frequency -= 86; break; } case 5: { symbolTime = 186; spacing = 10.7666; frequency -= 122; break; } case 8: { symbolTime = 128; spacing = 15.625; frequency -= 173; break; } case 11: { symbolTime = 93; spacing = 10.766; frequency -= 131; break; } case 16:{ symbolTime = 64; spacing = 15.625; frequency -= 177; break; } //default here is THOR22 default:{ symbolTime = 46; spacing = 21.533; frequency -= 262; break; } } while((length + 10) > round1){ if (nextChar == 0){ byte k = byte(thorMessage[round1]); nextChar = pgm_read_word_near(varicodeTable + k); //Counting how many bits there are in current varicode character //Result will be stored to bitsInChar bitsInChar = 0; byte x = 0; byte currentBit = 0; while (currentBit == 0){ currentBit = bitRead(nextChar,(15-x)); x++; } bitsInChar = 17 -x; round1++; } //Transmitting happends in this long if-block if (thorTimer <= millis()){ thorTimer = thorTimer + symbolTime; //FEC //If there is no character to send, idle (<NUL>) will be transmitted if (inFec < 1 && nextChar == 0){ if (idleTime > 10){ thorOn = 0; //Serial.print("transmitter off"); //Serial.print("\n"); AD9850_reset(); } else {nextChar = pgm_read_word_near(0); idleTime++; } } // If there is next character ready, then check if there is yet enough room for it in fecIn if ((nextChar != 0) && (21-inFec > bitsInChar)){ //Writing next char to fecIn for (byte x = 0; x < bitsInChar; x++){ bitWrite(fecIn, (inFec+7), bitRead(nextChar,(bitsInChar-x-1))); inFec++; } nextChar = 0; } //ACTUAL FEC-CODING: for (byte x=0; x<2; x++){ dibit1 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,1)) ^ (bitRead(fecIn,0)); fecOut = fecOut << 1; bitWrite(fecOut, 0, dibit1); dibit2 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,5)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,0)); fecOut = fecOut << 1; bitWrite(fecOut, 0, dibit2); fecIn = fecIn >> 1; inFec--; if (inFec < 0) { inFec = 0; } } //INTERLEAVER is simply bit shifter. or "delayer" //TO INTERLIEVER: bitWrite(toTx,3, bitRead(fecOut,3)); //No delay for 1.bit, it is goes straight to toTx bitWrite(interleaver2, 0, bitRead(fecOut,2));//delay 10 bits, or shifts bitWrite(interleaver3, 0, bitRead(fecOut,1));//delay 20 bits, or shifts bitWrite(interleaver4, 0, bitRead(fecOut,0));//delay 30 bits, or shifts //FROM INTERLIEVER: // toTx,3, is already filled bitWrite(toTx,2,(bitRead(interleaver2,10))); bitWrite(toTx,1,(bitRead(interleaver3,20))); bitWrite(toTx,0,(bitRead(interleaver4,30))); //Interleaver is bitshifter, so lets shift it: interleaver2= interleaver2 << 1; interleaver3 = interleaver3 << 1; interleaver4 = interleaver4 << 1; // toTx is now ready to be transmitted, and it contains 4-bit gray-code, corresponding to tone difference for next symbol or "tone" //those 4 bits are at places 0-3 on toTx, which is actually byte ofcourse. Bytes at places 4-7 are simply 0. switch (toTx){ //Converting gray code to actual tone (or freq) //Graycode is readed as decimal, thats why "wrong" sequency of case-desimals // Adding two (next+2) is due to nature of IFK+ -modulation used at THOR //Tone0 0000 case 0: next = next+2; break; //Tone1 0001 case 1:next = next+3; break; //Tone 2: 0011 case 3:next = next+5; break; //Tone3: 0010 case 2:next = next+4; break; //tone4: 0110 case 6:next = next+8; break; //tone5 0111 case 7:next = next+9; break; //tone6 0101 case 5:next = next+7; break; //tone7 0100 case 4:next = next+6; break; //tone8 1100 case 12:next = next+14; break; //tone 9 1101 case 13:next = next+15; break; //tone 10 1111 case 15:next = next+17; break; //tone11 1110 case 14:next = next+16; break; //tone12 1010 case 10:next = next+12; break; //tone13 1011 case 11:next = next+13; break; //tone 14 1001 case 9:next = next+11; break; //tone15 1000 case 8:next = next+10; break; } if (next > 17){ next = next - 18; } //Actual frequency change, modulation, happends now if (thorOn == 1){ SetFrequency(frequency, float((next * spacing))); } } } SetFrequency(0); }Some Notes...
VaricodeTable
Varicode characters are defined on array of ints on PROGMEM. (Picture) Defining array is done such a odd-looking way in order to avoid mistakes, or at least to make possible to correct them.
Characters are taken from here. (Work of Nino Porcino IZ8BLY & M. Greenman ZL1BPU) where they are defined in varying lenght of bitrows. Those bitrows are converted to desimal values and stored to int. (Because in Arduino IDE only byte can be defined like B01010... not int, but there are varicode characters longer than eight bits, so int must be used to store them.)
When I was making this array, making one row was about ten-step process. And there are 256 rows...
Don't be confused when you find out that these are "MFSK-varicode characters", yes they are. Same are used on THOR.
Oddity report: character 'a' on row number 97 had to be dublicated. Otherwise characters with line numbers greater than 97 would not work correct, but 'a' would turn to be 'b' , 'b' would turn to be 'c' and so on ( or was it opposite..?). Reason is currently unknown.
ACTUAL FEC-CODING:
for (byte x=0; x<2; x++){
dibit1 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,1)) ^ (bitRead(fecIn,0));
fecOut = fecOut << 1;
bitWrite(fecOut, 0, dibit1);
dibit2 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,5)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,0));
fecOut = fecOut << 1;
bitWrite(fecOut, 0, dibit2);
fecIn = fecIn >> 1; inFec--;
if (inFec < 0) {inFec = 0;}}
INTERLEAVER is simply bit shifter. or "delayer"
TO INTERLIEVER:
bitWrite(toTx,3, bitRead(fecOut,3)); No delay for 1.bit, it is goes straight to toTx
bitWrite(interleaver2, 0, bitRead(fecOut,2));delay 10 bits, or shifts
bitWrite(interleaver3, 0, bitRead(fecOut,1));delay 20 bits, or shifts
bitWrite(interleaver4, 0, bitRead(fecOut,0));delay 30 bits, or shifts
FROM INTERLIEVER:
toTx,3, is already filled
bitWrite(toTx,2,(bitRead(interleaver2,10)));
bitWrite(toTx,1,(bitRead(interleaver3,20)));
bitWrite(toTx,0,(bitRead(interleaver4,30)));
Interleaver is bitshifter, so lets shift it:
interleaver2= interleaver2 << 1;
interleaver3 = interleaver3 << 1;
interleaver4 = interleaver4 << 1;
// toTx is now ready to be transmitted, and it contains 4-bit gray-code, corresponding to tone difference for next symbol or "tone"
mark